/*
Copyright (c) 2012 VBChunguk

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

#include "Danmaku.h"

namespace Danmaku
{
	void Danmaku::Action()
	{
		x += cos(direction)*speed;
		y += -sin(direction)*speed;
	}

	bool GlobalScript::OpenScript(string filename)
	{
		fstream fi;
		fi.open(filename, ios_base::in+ios_base::binary);

		try {
			// 헤더 읽기
			fi.read(reinterpret_cast<char *>(&Header), sizeof(ScriptHeader));
			if(Header.magic[0] != 'S' || Header.magic[1] != 'C' || Header.magic[2] != 'R' || Header.magic[3] != 'T') {
				throw DanmakuException();
			}

			// 엔트리 목록 읽기
			fi.seekg(Header.EntryPos);
			fi.read(reinterpret_cast<char *>(&EntryHeader), 16);
			fi.seekg(EntryHeader.position);

			uint32_t ByteCount = 0;
			unsigned int i = 0;
			while(ByteCount < EntryHeader.length) {
				EntryData temp;
				fi.read(reinterpret_cast<char *>(&temp), 5);
				char *tname = new char[temp.namelen];
				fi.read(tname, temp.namelen);
				temp.name = tname;
				Entry.push_back(temp);
				if(temp.name == "MainLoop") MainLoopEntry = i;
				else if(temp.name == "Initialize") InitializeEntry = i;
				else if(temp.name == "Draw") DrawLoopEntry = i;
				ByteCount += (5+temp.namelen);
				i++;
			}

			if(MainLoopEntry == 0xFFFFFFFF) throw DanmakuException(DanmakuException::missing);

			// 데이터 섹션 읽기
			fi.seekg(Header.DataPos);
			fi.read(reinterpret_cast<char *>(&DataHeader), 16);
			fi.seekg(DataHeader.position);

			DataHeader.data = new char[DataHeader.length];
			fi.read(DataHeader.data, DataHeader.length);

			// 코드 섹션 읽기
			fi.seekg(Header.CodePos);
			fi.read(reinterpret_cast<char *>(&CodeHeader), 16);
			fi.seekg(CodeHeader.position);

			CodeHeader.data = new char[CodeHeader.length];
			fi.read(CodeHeader.data, CodeHeader.length);
		} catch(DanmakuException &e) {
			fi.close();
			if(e.GetErrorCode() == DanmakuException::ok) return true;
			return false;
		} catch(...) {
			fi.close();
			return false;
		}
		fi.close();

		ExecuteCustomEntry(InitializeEntry);
		return true;
	}

	bool GlobalScript::MainLoop()
	{
		try {
			if(!ExecuteCustomEntry(MainLoopEntry)) throw DanmakuException();
			if(!DanmakuActionLoop()) throw DanmakuException();
		} catch(DanmakuException &e) {
			if(e.GetErrorCode() == DanmakuException::ok) return true;
			return false;
		} catch(...) {
			return false;
		}
		return true;
	}
	bool GlobalScript::DanmakuActionLoop()
	{
		list<Danmaku>::iterator i;
		try {
			for(i = DanmakuList.begin(); i != DanmakuList.end(); i++) {
				if(i->isActivated()) {
					if(!i->isIgnoreAction()) i->Action();
					ExecuteCustomEntry(i->GetEntryID(), *i);
				}
			}
		} catch(DanmakuException &e) {
			if(e.GetErrorCode() == DanmakuException::ok) return true;
			return false;
		} catch(...) {
			return false;
		}
		return true;
	}

	bool GlobalScript::ExecuteCustomEntry(unsigned int ID)
	{
		uint32_t ExecutePosition = Entry[ID].position;
		char *EP;
		Instruction ins;

		EP = CodeHeader.data+ExecutePosition;
		try {
			Stack[SP++] = 0xFEFEFEFE;
			while(1) {
				memcpy(&ins, EP, 6);
				EP += 6;
				//memcpy(&ins.data, EP, ins.len);
				ins.data = EP;
				EP += ins.len;

				DanmakuException result;
				result = ExecuteInstruction(ins, &EP, NULL);
				if(result.GetErrorCode() != DanmakuException::ok) throw result;
			}
		} catch(DanmakuException &e) {
			if(e.GetErrorCode() == DanmakuException::funcreturn) return true;
			return false;
		} catch(...) {
			return false;
		}
		return true;
	}
	bool GlobalScript::ExecuteCustomEntry(unsigned int ID, Danmaku &danmaku)
	{
		uint32_t ExecutePosition = Entry[ID].position;
		char *EP;
		Instruction ins;

		EP = CodeHeader.data+ExecutePosition;
		try {
			Stack[SP++] = 0xFEFEFEFE;
			while(1) {
				memcpy(&ins, EP, 6);
				EP += 6;
				//memcpy(&ins.data, EP, ins.len);
				ins.data = EP;
				EP += ins.len;

				DanmakuException result;
				result = ExecuteInstruction(ins, &EP, &danmaku);
				if(result.GetErrorCode() != DanmakuException::ok) throw result;
			}
		} catch(DanmakuException &e) {
			if(e.GetErrorCode() == DanmakuException::funcreturn) return true;
			return false;
		} catch(...) {
			return false;
		}
		return true;
	}

	DanmakuException GlobalScript::ExecuteInstruction(Instruction &ins, char **EP, Danmaku *danmaku)
	{
		//char *EP;
		//EP = *tEP;
		if((ins.prop & PropMask) != PropMask) {
			DanmakuException e(DanmakuException::propmask);
			return e;
		}
		try {
			switch(ins.ID)
			{
				// 레지스터/스택 관련
			case 0x00:
				A = GetInstructionArg<uint32_t>(&ins.data);
				break;
			case 0x01:
				B = GetInstructionArg<uint32_t>(&ins.data);
				break;
			case 0x02:
				X = GetInstructionArg<uint32_t>(&ins.data);
				break;
			case 0x03:
				Y = GetInstructionArg<uint32_t>(&ins.data);
				break;
			case 0x04:
				Stack[SP++] = A;
				break;
			case 0x05:
				Stack[SP++] = B;
				break;
			case 0x06:
				A = Stack[--SP];
				break;
			case 0x07:
				B = Stack[--SP];
				break;
			case 0x08:
				A = Stack[BP + GetInstructionArg<int32_t>(&ins.data)];
				break;
			case 0x09:
				B = Stack[BP + GetInstructionArg<int32_t>(&ins.data)];
				break;
			case 0x0A:
				BP = SP;
				break;
			case 0x0B:
				BP = Stack[--SP];
				break;
			case 0x0C:
				Stack[SP++] = BP;
				break;
			case 0x0D:
				A = ReadDWORD(DataHeader.data+GetInstructionArg<uint32_t>(&ins.data));
				break;
			case 0x0E:
				WriteDWORD(DataHeader.data+GetInstructionArg<uint32_t>(&ins.data), A);
			case 0x0F:
				Stack[SP++] = GetInstructionArg<uint32_t>(&ins.data);
				break;
				// 연산 관련
			case 0x10: // A + uint32_t
				A += GetInstructionArg<uint32_t>(&ins.data);
				break;
			case 0x11: // B + uint32_t
				B += GetInstructionArg<uint32_t>(&ins.data);
				break;
			case 0x12: // Stack[SP] + float
				{
					float temp;
					temp = int2float(Stack[SP-1]);
					temp += GetInstructionArg<float>(&ins.data);
					Stack[SP-1] = float2int(temp);
				}
				break;
				// 비교 관련
			case 0x20: // A와 값을 비교
				{
					uint32_t target = GetInstructionArg<uint32_t>(&ins.data);
					if(A == target) Z = true;
					else Z = false;
					if(A >= target) C = true;
					else C = false;
				}
				break;
			case 0x21: // B와 값을 비교
				{
					uint32_t target = GetInstructionArg<uint32_t>(&ins.data);
					if(B == target) Z = true;
					else Z = false;
					if(B >= target) C = true;
					else C = false;
				}
				break;
			case 0x22: // A와 B를 비교
				{
					if(A == B) Z = true;
					else Z = false;
					if(A >= B) C = true;
					else C = false;
				}
				break;
				// 분기 관련
			case 0x30: // Z가 참일 때: 숏 점프
				{
					if(Z) {
						int16_t jump;
						jump = GetInstructionArg<int16_t>(&ins.data);
						*EP += jump;
					}
				}
				break;
			case 0x31: // Z가 거짓일 때: 숏 점프
				{
					if(!Z) {
						int16_t jump;
						jump = GetInstructionArg<int16_t>(&ins.data);
						*EP += jump;
					}
				}
				break;
			case 0x32: // C가 참일 때: 숏 점프
				{
					if(C) {
						int16_t jump;
						jump = GetInstructionArg<int16_t>(&ins.data);
						*EP += jump;
					}
				}
				break;
			case 0x33: // C가 거짓일 때: 숏 점프
				{
					if(!C) {
						int16_t jump;
						jump = GetInstructionArg<int16_t>(&ins.data);
						*EP += jump;
					}
				}
				break;

				//

			case 0x80: // 텍스트 출력
				{
					int x, y;
					uint32_t text_ptr;
					wchar_t *text;
					x = Stack[--SP];
					y = Stack[--SP];
					text_ptr = Stack[--SP];
					text = reinterpret_cast<wchar_t *>(DataHeader.data+text_ptr);

					RECT rc = {x, y, 640, 480};
					Font->DrawTextW(NULL, text, -1, &rc, DT_TOP | DT_LEFT, 0xff000000);
				}
				break;
			case 0x81: // 텍스쳐 출력
				{
					float x, y, rot, xscale, yscale;
					RECT rc;
					IDirect3DTexture9 *tex;

					x = int2float(Stack[--SP]);
					y = int2float(Stack[--SP]);
					rot = int2float(Stack[--SP]);
					xscale = int2float(Stack[--SP]);
					yscale = int2float(Stack[--SP]);
					rc = d3d::CreateRectData(
						Stack[--SP], Stack[--SP],
						Stack[--SP], Stack[--SP]);
					tex = reinterpret_cast<IDirect3DTexture9 *>(Stack[--SP]);
					d3d::DrawSprite(ParentDevice, x, y, rot, xscale, yscale, rc, &tex);
				}
				break;
				// 탄막 관련
			case 0x90: //탄막 생성
				{
					Danmaku temp;
					uint32_t InitEntry;
					uint32_t ActionEntry;

					InitEntry = Stack[--SP];
					ActionEntry = Stack[--SP];

					temp.SetActionEntry(ActionEntry);
					ExecuteCustomEntry(InitEntry, temp);

					DanmakuList.push_back(temp);
				}
				break;
			case 0x91: // 탄막 위치 이동: (x, y)
				{
					if(danmaku == NULL) throw DanmakuException(); // 탄막이 필요함

					float x, y;
					x = int2float(Stack[--SP]);
					y = int2float(Stack[--SP]);

					danmaku->SetDanmakuPos(x, y);
				}
				break;
			case 0x92: // 탄막 방향 변경
				{
					if(danmaku == NULL) throw DanmakuException();

					float direction;
					direction = int2float(Stack[--SP]);

					danmaku->SetDanmakuDir(direction);
				}
				break;
			case 0x192: // 탄막 방향 가져오기
				{
					if(danmaku == NULL) throw DanmakuException();

					A = float2int(danmaku->GetDanmakuDir());
				}
				break;
			case 0x93: // 탄막 속도 변경
				{
					if(danmaku == NULL) throw DanmakuException();

					float speed;
					speed = int2float(Stack[--SP]);

					danmaku->SetDanmakuSpd(speed);
				}
				break;
			case 0x193: // 탄막 속도 가져오기
				{
					if(danmaku == NULL) throw DanmakuException();

					A = float2int(danmaku->GetDanmakuSpd());
				}
				break;
			case 0x94: // 탄막 활성화
				{
					if(danmaku == NULL) throw DanmakuException();

					danmaku->ActivateDanmaku();
				}
				break;
				// 시스템 관련
			case 0xFD: // Z/C 업데이트
				UpdateRegister(GetInstructionArg<uint16_t>(&ins.data));
				break;
			case 0xFE: // 속성 마스크 설정
				PropMask = GetInstructionArg<uint16_t>(&ins.data);
				break;
			case 0x1FE: // 속성 마스크 설정: 레지스터 A
				PropMask = static_cast<uint16_t>(A);
				break;
			case 0x2FE: // 속성 마스크 가져오기
				A = PropMask;
				break;
			case 0xFF: // 리턴
				{
					if(SP == 0) throw DanmakuException(DanmakuException::reterror);
					uint32_t offset = Stack[--SP];
					if(offset == 0xFEFEFEFE) throw DanmakuException(DanmakuException::funcreturn);
				}
				break;
			}
		} catch(DanmakuException &e) {
			return e;
		} catch(...) {
			DanmakuException e;
			return e;
		}

		DanmakuException e(0);
		return e;
	}

	bool GlobalScript::DrawLoop()
	{
		list<Danmaku>::iterator i;
		try {
			if(DrawLoopEntry != 0xFFFFFFFF) ExecuteCustomEntry(DrawLoopEntry);
			for(i = DanmakuList.begin(); i != DanmakuList.end(); i++) {
				// TODO: 여러 가지 탄막 그래픽 지원
				d3d::DrawSprite(
					ParentDevice,
					i->GetDanmakuPosX(), i->GetDanmakuPosY(), i->GetDanmakuDir()-(0.5f*3.141592f), 1, 1,
					d3d::CreateRectData(32, 16, 47, 31), &BulletTex[0]);
			}
		} catch(DanmakuException &e) {
			if(e.GetErrorCode() == 0) return true;
			return false;
		} catch(...) {
			return false;
		}
		return true;
	}

	bool GlobalScript::ResetDevice(D3DPRESENT_PARAMETERS d3dpp)
	{
		if(ParentDevice) {
			d3d::Release<ID3DXFont *>(Font);
			(*ParentDevice)->Reset(&d3dpp);

			D3DXCreateFont(
				*ParentDevice,
				15, 0, 400, 0, FALSE,
				949, 255, 4, 0, L"돋움",
				&Font);
		}
		return true;
	}

	void GlobalScript::WriteDWORD(char *target, uint32_t value)
	{
		memcpy(target, &value, sizeof(uint32_t));
	}
	uint32_t GlobalScript::ReadDWORD(char *target)
	{
		uint32_t temp;
		memcpy(&temp, target, sizeof(uint32_t));
		return temp;
	}

	void GlobalScript::UpdateRegister(uint16_t flag)
	{
		uint16_t ABXY;
		uint32_t *target;
		ABXY = flag&3;
		
		switch(ABXY) {
		case 0: target = &A; break;
		case 1: target = &B; break;
		case 2: target = &X; break;
		case 3: target = &Y; break;
		default: throw DanmakuException(DanmakuException::argerror);
		}

		if(*target) Z = false;
		else Z = true;
	}

	uint32_t float2int(float arg)
	{
		int_float inf;
		inf._float = arg;
		return inf._int;
	}
	float int2float(uint32_t arg)
	{
		int_float inf;
		inf._int = arg;
		return inf._float;
	}
}
